package com.cn.lotary.base.web;

import com.cn.lotary.base.dao.AbstractEntity;
import com.cn.lotary.base.dao.BaseDao;
import com.cn.lotary.util.LoggerUtils;
import com.cn.lotary.util.MessageBundle;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;
import javax.persistence.metamodel.SingularAttribute;
import org.primefaces.component.datatable.DataTable;
import org.primefaces.event.data.SortEvent;

/**
 *
 * @author Jason
 */
public abstract class BaseController<T extends AbstractEntity> implements BaseControllerInterface<T>, Serializable {

    /**
     * get BaseDao
     *
     * @return BaseDao
     */
    protected abstract BaseDao<T> dao();

    /**
     * the value for select item label
     */
    protected abstract SingularAttribute selectItemLabel();
    private LazyEntityDataModel<T> dataModel;
    private T entity;
    //test

    public BaseController() {
    }

    /**
     * get Request Parameter
     *
     * @param key
     * @return String
     */
    public String requestParameter(String key) {
        try {
            Map<String, String> requestParameter = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
            return requestParameter.get(key);
        } catch (NullPointerException e) {
            LoggerUtils.debug("[no key :]" + key);
        } catch (Exception e) {
            LoggerUtils.error(e);
        }
        return null;
    }

    /**
     * 添加新增条件
     *
     * @param t
     * @return FacesMessage
     */
    protected FacesMessage createCondition() {
        return null;
    }

    @Override
    public void create() {
        LoggerUtils.debug("[cell create] "+entity);
        FacesMessage message = createCondition();
        if (message != null) {
            MessageBundle.autoMessage(message);
            return;
        }
        message = dao().create(entity);

        if (message != null) {
            MessageBundle.autoMessage(message);
            if (message.getSeverity().equals(FacesMessage.SEVERITY_INFO)) {
                reset();
            }
        }
    }

    //TODO: final can not overwrite
    public final void remove() {
        remove(entity);
    }

    @Override
    public void remove(T t) {
        //TODO:  delete condaition
        MessageBundle.autoMessage(dao().remove(t));
    }

    /**
     * go to list page, if update successful.
     *
     */
    @Override
    public void update() {
        FacesMessage message = dao().update(entity);
        MessageBundle.autoMessage(message);
        //if (message.getSeverity().equals(FacesMessage.SEVERITY_INFO)) {
        //  reset();
        //FacesContext.getCurrentInstance().getExternalContext().redirect("a.hxtml");            
        //}
    }

    /**
     * get BaseConverter
     *
     * @return BaseConverter
     */
    public BaseConverter<T> getConverter() {
        return new BaseConverter<T>(dao());
    }

    /**
     *
     * get primefaces LazyDataModel<T>
     *
     * @throws FacesException, must lazy="true"
     * @exception INFO: javax.faces.FacesException: Data type should be
     * java.util.List or javax.faces.model.ListDataModel instance to be
     * sortable.
     * @return LazyDataModel<T>
     */
    public LazyEntityDataModel<T> getDataModel() {
        if (dataModel == null) {
            dataModel = new LazyEntityDataModel<T>(getDataModelFilter(), dao());
        }

        return dataModel;
    }

    /**
     * 如果默认 lazyEntityDataModel 需要添加条件，请重写此方法<br/>
     *
     * if default lazyEntityDataModel need add condition, please Override this
     * method<br/>
     *
     *
     * @return datamodel filter, eg: id > 10 and o.id < 1000
     */
    protected String getDataModelFilter() {
        return null;
    }

    public void setDataModel(LazyEntityDataModel<T> dataModel) {
        this.dataModel = dataModel;
    }

    /**
     * @deprecated get all entitys
     *
     * @return ALL entity
     */
    public List<T> all() {
        return dao().findAll();
    }

    private void setEntityByRequestEntityId() {
        try {
//            Long id = Long.valueOf(requestParameter("id"));
            String key= requestParameter("id");
            if(key != null && !"".equals(key)){
                entity = dao().find(requestParameter("id"));            
            }
        } catch (Exception e) {
            LoggerUtils.debug(e);
        }
    }

    /**
     * get getEntity()
     *
     * @return entity
     */
    public T getEntity() {
        setEntityByRequestEntityId();
        if (entity == null) {
            try {
                entity = dao().getEntityClass().newInstance();
            } catch (Exception e) {
                LoggerUtils.error(e);
            }
        }
        return entity;
    }
//    public T getSelected() {
//        if (selected == null) {
//            try {
//                selected = (T) dao().getEntityClass().newInstance();
//            } catch (Exception e) {
//                //TODO: logger
//                System.err.println(ex.getMessage());
//            }
//        }
//        return selected;
//    }
//
//    public void setSelected(T selected) {
//        this.selected = selected;
//    }

    public void setEntity(T entity) {
        this.entity = entity;
    }

    /**
     * use for select on the view
     *
     * @param selectOne
     * @return SelectItem[]
     */
    //TODO: 抽象条件
    public SelectItem[] selectItemsI18n(Boolean selectOne) {
        return dao().getSelectItems(selectItemLabel(), null, null, selectOne, true);
    }

    public SelectItem[] selectItems(Boolean selectOne) {
        return dao().getSelectItems(selectItemLabel(), null, null, selectOne);
    }

    /**
     * clear up
     */
    public void reset() {
        entity = null;
    }

    /**
     *
     *
     * @return entity is new
     */
    public boolean isNew() {
        return getEntity().isNew();
    }

    /**
     * <br/validation unique column <br/>
     *
     * id must like addName or editName todo move<br/>
     *
     * this function to entity<br/>
     *
     * remark: Entity
     *
     * remark:unique can't work, find way to solve it, this is temporary way
     *
     * @param context
     * @param toValidate
     * @param value
     * @see dao validationUniqueColumn
     * @deprecated
     */
    //TODO:not work on page material status, attribute name
    @Deprecated
    public void unique(FacesContext context, UIComponent toValidate, Object value) {
        String viewId = toValidate.getId();

        if (verificationId(viewId)) {
            MessageBundle.validatorMessage(
                    FacesMessage.SEVERITY_ERROR,
                    toValidate, context, viewId,
                    "system parameter error, call developer");
            return;
        }
        if (value == null || value.equals("")) {
            MessageBundle.validatorMessage(
                    FacesMessage.SEVERITY_WARN, toValidate,
                    context, value.toString(), MessageBundle.REQUIRED);
            return;
        }

        String field = getIdByViewId(viewId);
        List<T> findByColumn = dao().findByField(field, value);
        int size = findByColumn.size();
        if (entity.isNew()) {
            if (size > 0) {
                MessageBundle.validatorMessage(
                        FacesMessage.SEVERITY_WARN, toValidate,
                        context, value.toString(), MessageBundle.ALREADY_EXISTS);
            }
        } else {
            if (size == 1) {
                try {
                    T o = findByColumn.get(0);
                    Object id = o.getId();

                    if (id != entity.getId()) {
                        MessageBundle.validatorMessage(
                                FacesMessage.SEVERITY_WARN, toValidate, context,
                                value.toString(), MessageBundle.ALREADY_EXISTS);
                    }
                } catch (NullPointerException e) {
                    LoggerUtils.error(e);
                } catch (SecurityException e) {
                    LoggerUtils.error(e);
                } catch (Exception e) {
                    LoggerUtils.error(e);
                }
            }
        }

    }

    protected boolean verificationId(String id) {
        boolean outcome = false;

        if (!(id.startsWith("add") || id.startsWith("edit"))) {
            String message = "this input element id must start with add or edit, and after 'add' or 'edit' must is entity attburt name";
            MessageBundle.showError(message);
            outcome = true;
        }

        return outcome;
    }

    /**
     *
     * @param str xhtml view id
     * @return field name
     */
    protected String getIdByViewId(String str) {
        str = str.replace("add", "").replace("edit", "");
        String[] a = str.split("");
        return str.replaceFirst(a[1], a[1].toLowerCase());
    }

    protected String getSetForegin(String str) {
        //str = str.replace("add", "").replace("edit", "");
        String[] a = str.split("");
        return "set" + str.replaceFirst(a[1], a[1].toUpperCase());
    }

    /**
     * not work with composite component
     *
     *
     * <br/> this method can add foriegn entity<br/>
     *
     * value must like<b>#{entityField}<b/> <br/>
     *
     * id must like <b>(add,edit) + Entity name<b/><br/>
     *
     * eg:
     * <code> <h:input id="addType" value="#{name}" validator="#{entityController.foreign}"/></code>
     *
     *
     * @exception if foreign is NotNull, must using required="true" else
     * javax.ejb.EJBException
     * @param context
     * @param toValidate
     * @param value
     */
    //TODO:not work with composite component
    public void foreign(FacesContext context, UIComponent toValidate, Object value) {
        //TODO: 校验 value
        String viewId = toValidate.getId();
        if (verificationId(viewId) || value == null || value == "") {
            return;
        }
        String foreignField = toValidate.getValueExpression("value").getExpressionString().replace("#{", "").replace("}", "");
        if (foreignField.contains("cc.attrs.")) {
            MessageBundle.validatorMessage(FacesMessage.SEVERITY_FATAL, toValidate, context, value.toString(), "this method can not using composite component");
            return;
        }
        try {
            Class<?> entityClass = entity.getClass();
            String field = getIdByViewId(viewId);
            Field declaredField = entityClass.getDeclaredField(field);
            Class<?> foreignClass = declaredField.getType();
            Method method = entityClass.getMethod(getSetForegin(field), foreignClass);
            Object foreginEntity = dao().findByColumnAndEntityClass(foreignClass, foreignField, value);

            if (foreginEntity != null) {
                method.invoke(entity, foreginEntity);
            } else {
                MessageBundle.validatorMessage(FacesMessage.SEVERITY_WARN, toValidate, context, value.toString(), MessageBundle.DOES_NOT_EXIST);
            }
        } catch (IllegalAccessException e) {
            LoggerUtils.error(e);
        } catch (InvocationTargetException e) {
            LoggerUtils.error(e);
        } catch (IllegalArgumentException e) {
            LoggerUtils.error(e);
        } catch (NoSuchMethodException e) {
            LoggerUtils.error(e);
        } catch (SecurityException e) {
            LoggerUtils.error(e);
        } catch (NoSuchFieldException e) {
            LoggerUtils.error(e);
        } catch (Exception e) {
            LoggerUtils.error(e);
        }
    }

    protected void foreign(FacesContext context, UIComponent toValidate, Object value, SingularAttribute foreignField) {
        //TODO: 校验 value
        String viewId = toValidate.getId();
        if (verificationId(viewId) || value == null || value == "") {
            return;
        }
        try {
            Class<?> entityClass = entity.getClass();
            String field = getIdByViewId(viewId);
            Field declaredField = entityClass.getDeclaredField(field);
            Class<?> foreignClass = declaredField.getType();
            Method method = entityClass.getMethod(getSetForegin(field), foreignClass);
            Object foreginEntity = dao().findByColumnAndEntityClass(foreignClass, foreignField.getName(), value);

            if (foreginEntity != null) {
                method.invoke(entity, foreginEntity);
            } else {
                MessageBundle.validatorMessage(FacesMessage.SEVERITY_WARN, toValidate, context, value.toString(), MessageBundle.DOES_NOT_EXIST);
            }
        } catch (IllegalAccessException e) {
            LoggerUtils.error(e);
        } catch (InvocationTargetException e) {
            LoggerUtils.error(e);
        } catch (IllegalArgumentException e) {
            LoggerUtils.error(e);
        } catch (NoSuchMethodException e) {
            LoggerUtils.error(e);
        } catch (SecurityException e) {
            LoggerUtils.error(e);
        } catch (NoSuchFieldException e) {
            LoggerUtils.error(e);
        } catch (Exception e) {
            LoggerUtils.error(e);
        }
    }

    /**
     * page redirect
     *
     * @param page page path
     */
    public void redirect(String page) {
        try {
            if (page != null && !"".equals(page)) {
                FacesContext.getCurrentInstance().getExternalContext().redirect(page);
            }
        } catch (IOException ex) {
            LoggerUtils.error(ex);
        }
    }

    /**
     * in testing
     *
     * <br/> this method can add foriegn entity<br/>
     *
     * value must like <b> #{entityField}<b/>
     *
     * <br/> id must like <b>(add,edit) + Entity name<b/> *
     *
     * eg: <h:input id="addType" value="#{name}"
     * converter="#{**.foreignConverter()"}/>
     *
     * @return ForeignConverter
     */
    protected ForeignConverter foreignConverter(SingularAttribute foreignField) {
        return new ForeignConverter<T>(foreignField, this);
    }    
}
